package com.codeyang.aspect;

import com.alibaba.fastjson.JSON;
import com.codeyang.annos.Log;
import com.codeyang.constans.Constants;
import com.codeyang.domain.OperLog;
import com.codeyang.domain.User;
import com.codeyang.enums.BusinessType;
import com.codeyang.service.OperLogService;
import com.codeyang.utils.AddressUtils;
import com.codeyang.utils.IpUtils;
import com.codeyang.utils.ShiroSecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 描述: 操作日志切面
 *
 * @author CodeYang_Site
 * @version 2021/5/5 23:47
 */
@Component
@Aspect
public class OperLogAspect {


    @Autowired
    private OperLogService operLogService;

    /**
     * 记录日志的操作
     * 使用了@Log 注解的就标志着要记录该操作
     *
     * @param point 切入点  --- 使用注解来作为切入点
     * @return 方法的返回参数
     */
    @Around("@annotation(com.codeyang.annos.Log)")
    public Object operLogRecord(ProceedingJoinPoint point) {
        // 环绕注解三板斧
        Object result = null;
        OperLog operLog = getOperLog(point);

        try {
            // 方法执行之前
            result = point.proceed(point.getArgs());
            // 方法执行成功
            //操作状态
            operLog.setStatus(Constants.STATUS_TRUE);
            //操作成功后,返回的参数
            operLog.setJsonResult(JSON.toJSONString(result));
        } catch (Throwable throwable) {
            // 方法执行失败
            //操作状态
            operLog.setStatus(Constants.STATUS_FALSE);
            //错误信息
            operLog.setErrorMsg(throwable.getMessage());
            throwable.printStackTrace();
        } finally {
            //失败成功都要执行
            // 记录日志
            try {
                operLogService.save(operLog);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        return result;
    }

    /**
     * 方法执行之前需要初始化的准备工作
     *
     * @param point
     * @return 操作日志对象
     */
    private OperLog getOperLog(ProceedingJoinPoint point) {
        OperLog operLog = new OperLog();
        // 执行的cotroller中的方法
        // 1 获取到方法签名
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        // 2 get method
        Method method = methodSignature.getMethod();
        // 3 get 方法名称 // 如 deleteDeptByIds
        String methodName = method.getName();
        // 4 要求是 全类名+方法名() com.....method()
        // 5 获取目标对象
        Object target = point.getTarget();
        operLog.setMethod(target.getClass().getName() + "." + methodName + "()");

        // 请求方法类型 restfull
        // 1 get request
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        operLog.setRequestMethod(request.getMethod());
        // 操作类型 后台用户|put用户
        //先写死
        operLog.setOperatorType(1);
        // 操作用户名称
        // shiro集成转换
        User user = ShiroSecurityUtils.getCurrentUser();
        operLog.setOperName(user.getUserName());
        // 操作的url
        operLog.setOperUrl(request.getRequestURL().toString());
        // 操作参数与数据
        /**
         * {
         *     key:参数名称
         *     value:参数值
         * }
         */


        //1 spring 获取方法局部变量名称 -- key
        LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = localVariableTableParameterNameDiscoverer.getParameterNames(method);
        //2 切点 ---val
        Object[] args = point.getArgs();
        //3 构建 map json
        Map<String, Object> methodParamMap = new HashMap<>();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                // key 参数名称
                String parameterName = parameterNames[i];
                // val 参数值
                Object arg = args[i];
                // 排除掉 req resp session 三个参数因为无法被序列化 没有实现序列化接口
                // 验证失败跳过
                if (!validateArgs(arg)) {
                    continue;
                }

                methodParamMap.put(parameterName, arg);
            }
        }
        operLog.setOperParam(JSON.toJSONString(methodParamMap));
        // 操作的ip
        operLog.setOperIp(IpUtils.getIpAddr(request));
        // 操作的ip 地点归属
        operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
        // 操作时间
        operLog.setOperTime(new Date());
        // 处理注解标签值
        Log log = method.getAnnotation(Log.class);
        String title = log.title();
        BusinessType businessType = log.businessType();
        operLog.setTitle(title);
        operLog.setBusinessType(businessType.getCode() + "");
        return operLog;
    }

    /**
     * 验证参数是否是req resp session
     *
     * @param arg
     * @return
     */
    private boolean validateArgs(Object arg) {
        if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse || arg instanceof HttpSession) {
            return false;
        }
        return true;

    }
}
